Skip to content

Feat: 모의지원 전체 흐름 연결 및 재도전·삭제 기능 개선 [JDDEV-98]#103

Open
minnngo wants to merge 6 commits into
developfrom
feature/JDDEV-98-home_retry—api-feat

Hidden character warning

The head ref may contain hidden characters: "feature/JDDEV-98-home_retry\u2014api-feat"
Open

Feat: 모의지원 전체 흐름 연결 및 재도전·삭제 기능 개선 [JDDEV-98]#103
minnngo wants to merge 6 commits into
developfrom
feature/JDDEV-98-home_retry—api-feat

Conversation

@minnngo

@minnngo minnngo commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

🔗 관련 이슈

  • JDDEV-98

📝 개요

  • 홈 화면의 모의지원 재도전·삭제 기능을 API와 연결하고, 자소서 작성부터 분석 결과까지의 데이터 흐름을 안정화했습니다.
  • LNB 최근 항목, 알림, 화면 스크롤 및 공통 모달 동작을 개선했습니다.

⌨️ 작업 상세 내용

  • 저장된 공고 기반 모의지원 생성 및 연결 공고 조회 API 연동
  • 홈·결과 화면의 재도전 및 모의지원 단건 삭제 API 연동
  • 재도전 중복 요청 방지 및 sequence 기반 지원 회차 표시
  • 자소서 후보 문항 초기화, 문항 추가·삭제 및 답변 자동 저장 개선
  • 서버 문항 ID 동기화와 저장 요청 순서 제어로 자동 저장 오류 방지
  • 답변 저장 후 비동기 분석 요청 및 로딩 화면 연결
  • LNB 최근 지원 목록 조회·이동·자동 갱신 기능 연결
  • 알림 SSE 예외 처리, 알림별 이동 경로 및 읽지 않은 알림 표시 개선
  • 확인 모달을 공통 ModalNotice 컴포넌트로 통일
  • 홈·크레딧 화면에서 LNB를 고정하고 우측 콘텐츠만 스크롤되도록 수정

💡 코드 설명 및 참고사항

문항 추가·삭제와 자동 저장 요청이 동시에 실행되지 않도록 저장 큐와 revision을 사용했습니다. 서버에서 반환한 문항 ID를 기준으로 목록을 다시 동기화하며, 입력 중인 로컬 답변은 유지합니다.

모의지원 생성·재도전·삭제 후에는 변경 이벤트를 발생시켜 홈 목록과 LNB 최근 항목이 새로고침 없이 갱신되도록 했습니다.

결과 화면에서 재도전한 경우에만 재도전 안내 토스트가 표시되며, 홈 화면에서는 별도 안내 없이 바로 자소서 작성 화면으로 이동합니다.

🔍 리뷰 요구사항 (Reviewers)

  • 공고 확인부터 자소서 작성, 채점 요청, 분석 결과 조회까지 전체 모의지원 흐름이 정상적으로 동작하는지 확인 부탁드립니다.
  • 홈·결과 화면에서 재도전했을 때 새로운 지원 회차가 생성되고, 자소서 작성 화면으로 정상 이동하는지 확인 부탁드립니다.

Summary by CodeRabbit

  • New Features
    • Added deletion for draft and completed mock applications.
    • Added automatic question saving, improved question recovery, and more reliable updates during editing.
    • Added retry handling that prevents duplicate requests and disables retry actions while processing.
    • Sidebar now refreshes recent applications, supports direct navigation, and removes deleted items automatically.
    • Notification actions now provide improved navigation and error handling.
  • Bug Fixes
    • Improved confirmation, alert, and error modal presentation across application flows.
    • Improved page scrolling and full-height layouts.
    • Improved real-time notification connection handling.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates mock-apply question persistence, retry and deletion flows, result navigation, LNB recent-item and notification handling, ModalNotice layouts, confirmation dialogs, and viewport-based page containers.

Changes

Mock apply workflow

Layer / File(s) Summary
Mock-apply and question API contracts
jobdri/src/lib/api/mockApplies.ts, jobdri/src/lib/api/questions.ts
Question responses are normalized, save APIs return mapped questions, mock-apply creation accepts sequence data, deletion and linked-posting APIs are added, and retry requests are de-duplicated.
Question loading and synchronized editing
jobdri/src/app/mockApply/[mockApplyId]/page.tsx
Question loading, auto-save, add, update, delete, submit, retry messaging, job-posting linkage, and modal actions use ref-backed serialized persistence.
Retry, deletion, and result integration
jobdri/src/app/page.tsx, jobdri/src/app/mockApply/[mockApplyId]/result/*, jobdri/src/hooks/useReApply.ts, jobdri/src/components/mockApply/home/*
Retry redirects and disabled states are wired through result lists, completed versions use sequences, mock applies can be deleted, and result pages load linked job postings directly.

Sidebar and notification workflows

Layer / File(s) Summary
Sidebar callback and export contracts
jobdri/src/components/common/lnb/*, jobdri/src/components/common/searchbar/SearchBar.tsx
Folded sidebar search, credit, logout, and notification callbacks are forwarded through updated props and exports.
Recent mock-apply navigation
jobdri/src/components/common/lnb/Lnb.tsx
Recent mock applies are loaded, sorted, refreshed after changes, removed after deletion, and opened through their computed routes.
Notification routing and unread state
jobdri/src/components/common/lnb/LnbNotification.tsx, jobdri/src/lib/api/notification.ts
SSE responses are validated, notifications expose task identifiers, unread indicators reflect item state, and notification clicks mark items read before analysis routing.

Modal and viewport UI

Layer / File(s) Summary
ModalNotice layout and action contract
jobdri/src/components/common/modal/ModalNotice.tsx
ModalNotice adds centered layout support and typed action styling with layout-specific rendering.
Confirmation modal migration
jobdri/src/app/mockApply/**, jobdri/src/components/common/MockApplyTemplate.tsx, jobdri/src/components/mockApply/ResumeAnalysisLoading.tsx
Confirmation, alert, retry, exit, and failure dialogs use the updated ModalNotice types and action API.
Credit and mock-apply viewport containers
jobdri/src/app/credit/page.tsx, jobdri/src/app/mockApply/[mockApplyId]/page.tsx, jobdri/src/app/page.tsx, jobdri/src/components/common/lnb/Lnb.tsx
Page and sidebar wrappers adopt viewport-height sizing, overflow control, and nested scrolling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: 🎨 UI, ⭐ Feature

Suggested reviewers: yiyoonseo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 모의지원 전체 흐름 연결과 재도전·삭제 기능 개선이라는 PR의 핵심 변경을 잘 요약합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/JDDEV-98-home_retry—api-feat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
jobdri/src/app/mockApply/[mockApplyId]/result/page.tsx (1)

103-121: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Retry failure produces no user feedback — the catch is unreachable.

reApply (in useReApply) swallows errors internally and returns null rather than throwing, so await reApply(...) never rejects and this catch never runs. On a failed retry the modal stays open and no error toast is shown. Gate on the return value instead.

🐛 Proposed fix
-    try {
-      await reApply(resolvedMockApplyId, {
-        getRedirectPath: (result) =>
-          `/mockApply/${result.mockApplyId}?retry=1&sequence=${result.sequence}`,
-      });
-    } catch {
-      setIsRetryModalOpen(false);
-      showTopToast("재도전을 시작하지 못했어요. 잠시 후 다시 시도해주세요.");
-    }
+    const result = await reApply(resolvedMockApplyId, {
+      getRedirectPath: (retryResult) =>
+        `/mockApply/${retryResult.mockApplyId}?retry=1&sequence=${retryResult.sequence}`,
+    });
+
+    if (!result) {
+      setIsRetryModalOpen(false);
+      showTopToast("재도전을 시작하지 못했어요. 잠시 후 다시 시도해주세요.");
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/app/mockApply/`[mockApplyId]/result/page.tsx around lines 103 -
121, Update handleRetryConfirm to inspect the value returned by reApply instead
of relying on the unreachable catch block. When reApply returns null or another
failure result, close the retry modal and show the existing failure toast;
preserve the redirect callback and successful retry behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@jobdri/src/app/credit/page.tsx`:
- Around line 48-49: Update the parent content container’s overflow behavior
near the fixed-width main element to allow horizontal scrolling instead of
clipping. Change the `overflow-x-hidden` behavior on the surrounding `<div>` to
`overflow-x-auto`, while preserving the existing vertical scrolling and `main`
width constraints.

In `@jobdri/src/components/common/lnb/Lnb.tsx`:
- Around line 262-273: Guard newNotification.type in the
subscribeToNotificationStream callback before invoking startsWith, preserving
the loadRecentItems refresh when the type is present and starts with
"ANALYSIS_ASYNC_". Treat missing types as non-matching so the notification is
still mapped and stored without throwing.
- Around line 285-308: Update the handleMockApplyDeleted handler in the Lnb
useEffect to select the first remaining recent item when the deleted item was
selected, matching loadRecentItems’s mappedItems[0]?.id ?? "" fallback; retain
the current selection when a different item is deleted and clear it only when no
items remain.

---

Outside diff comments:
In `@jobdri/src/app/mockApply/`[mockApplyId]/result/page.tsx:
- Around line 103-121: Update handleRetryConfirm to inspect the value returned
by reApply instead of relying on the unreachable catch block. When reApply
returns null or another failure result, close the retry modal and show the
existing failure toast; preserve the redirect callback and successful retry
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f625ec5-14b4-4107-999b-7e98b826d8df

📥 Commits

Reviewing files that changed from the base of the PR and between 4971576 and c823e80.

📒 Files selected for processing (25)
  • jobdri/src/app/credit/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/result/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx
  • jobdri/src/app/mockApply/job/create/page.tsx
  • jobdri/src/app/mockApply/job/loading/page.tsx
  • jobdri/src/app/mockApply/job/review/page.tsx
  • jobdri/src/app/page.tsx
  • jobdri/src/components/common/MockApplyTemplate.tsx
  • jobdri/src/components/common/lnb/Lnb.tsx
  • jobdri/src/components/common/lnb/LnbFolded.tsx
  • jobdri/src/components/common/lnb/LnbNotification.tsx
  • jobdri/src/components/common/lnb/LnbShared.tsx
  • jobdri/src/components/common/lnb/index.ts
  • jobdri/src/components/common/modal/ModalNotice.tsx
  • jobdri/src/components/common/searchbar/SearchBar.tsx
  • jobdri/src/components/mockApply/ResumeAnalysisLoading.tsx
  • jobdri/src/components/mockApply/home/ResultApplicationCard.tsx
  • jobdri/src/components/mockApply/home/ResultApplicationList.tsx
  • jobdri/src/components/mockApply/home/applicationHomeUtils.ts
  • jobdri/src/hooks/useReApply.ts
  • jobdri/src/lib/api/mockApplies.ts
  • jobdri/src/lib/api/notification.ts
  • jobdri/src/lib/api/questions.ts

Comment on lines +48 to +49
<div className="min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto">
<main className="mx-auto w-full max-w-[1320px] min-w-[1060px] px-18 pt-12 pb-60">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Allow horizontal scrolling for the fixed-width content.

main remains min-w-[1060px], but its parent uses overflow-x-hidden. When the viewport is narrower than the LNB plus 1060px, the right side of the credit content is clipped and cannot be reached. Use overflow-x-auto or relax the minimum width at smaller breakpoints.

Proposed fix
-      <div className="min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto">
+      <div className="min-h-0 min-w-0 flex-1 overflow-x-auto overflow-y-auto">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="min-h-0 min-w-0 flex-1 overflow-x-hidden overflow-y-auto">
<main className="mx-auto w-full max-w-[1320px] min-w-[1060px] px-18 pt-12 pb-60">
<div className="min-h-0 min-w-0 flex-1 overflow-x-auto overflow-y-auto">
<main className="mx-auto w-full max-w-[1320px] min-w-[1060px] px-18 pt-12 pb-60">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/app/credit/page.tsx` around lines 48 - 49, Update the parent
content container’s overflow behavior near the fixed-width main element to allow
horizontal scrolling instead of clipping. Change the `overflow-x-hidden`
behavior on the surrounding `<div>` to `overflow-x-auto`, while preserving the
existing vertical scrolling and `main` width constraints.

Comment on lines +262 to 273
void loadInitialNotifications();

const unsubscribe = subscribeToNotificationStream(
(newNotification) => {
const mappedNewItem = mapApiToLnbItem(newNotification);
setNotificationItems((prev) => [mappedNewItem, ...prev]);
setHasNotification(true);

if (newNotification.type.startsWith("ANALYSIS_ASYNC_")) {
void loadRecentItems();
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard newNotification.type before calling .startsWith.

mapNotificationType and LnbNotificationListItem both treat the notification type/apiType field as potentially absent (apiType?: string, notificationItem.type ?? "normal"), but this call doesn't guard it. If a payload lacks type, this throws inside the onmessage handler in notification.ts, which swallows it and mislogs it as a parse failure — silently skipping the recent-items refresh.

🛡️ Proposed fix
-        if (newNotification.type.startsWith("ANALYSIS_ASYNC_")) {
+        if (newNotification.type?.startsWith("ANALYSIS_ASYNC_")) {
           void loadRecentItems();
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void loadInitialNotifications();
const unsubscribe = subscribeToNotificationStream(
(newNotification) => {
const mappedNewItem = mapApiToLnbItem(newNotification);
setNotificationItems((prev) => [mappedNewItem, ...prev]);
setHasNotification(true);
if (newNotification.type.startsWith("ANALYSIS_ASYNC_")) {
void loadRecentItems();
}
},
void loadInitialNotifications();
const unsubscribe = subscribeToNotificationStream(
(newNotification) => {
const mappedNewItem = mapApiToLnbItem(newNotification);
setNotificationItems((prev) => [mappedNewItem, ...prev]);
setHasNotification(true);
if (newNotification.type?.startsWith("ANALYSIS_ASYNC_")) {
void loadRecentItems();
}
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/lnb/Lnb.tsx` around lines 262 - 273, Guard
newNotification.type in the subscribeToNotificationStream callback before
invoking startsWith, preserving the loadRecentItems refresh when the type is
present and starts with "ANALYSIS_ASYNC_". Treat missing types as non-matching
so the notification is still mapped and stored without throwing.

Comment on lines +285 to 308
useEffect(() => {
const handleMockApplyDeleted = (event: Event) => {
const mockApplyId = (event as CustomEvent<number>).detail;

setRecentItems((current) =>
current.filter((item) => item.id !== String(mockApplyId)),
);
setSelectedRecentItemId((current) =>
current === String(mockApplyId) ? "" : current,
);
};

window.addEventListener(
MOCK_APPLY_DELETED_EVENT,
handleMockApplyDeleted,
);

return () => {
window.removeEventListener(
MOCK_APPLY_DELETED_EVENT,
handleMockApplyDeleted,
);
};
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reselect fallback item after deleting the selected recent item.

Unlike loadRecentItems (which falls back to mappedItems[0]?.id ?? "" when the current selection is invalid), this handler just clears selectedRecentItemId to "" when the deleted item was selected, leaving nothing highlighted until the next full refresh.

♻️ Proposed fix
       setRecentItems((current) =>
         current.filter((item) => item.id !== String(mockApplyId)),
       );
       setSelectedRecentItemId((current) =>
-        current === String(mockApplyId) ? "" : current,
+        current === String(mockApplyId)
+          ? (recentItems.find((item) => item.id !== String(mockApplyId))?.id ?? "")
+          : current,
       );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/lnb/Lnb.tsx` around lines 285 - 308, Update the
handleMockApplyDeleted handler in the Lnb useEffect to select the first
remaining recent item when the deleted item was selected, matching
loadRecentItems’s mappedItems[0]?.id ?? "" fallback; retain the current
selection when a different item is deleted and clear it only when no items
remain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant